package com.zhijian.warehouse.manage.util.access;

import com.gg.reader.api.dal.GClient;
import com.gg.reader.api.dal.HandlerTagEpcLog;
import com.gg.reader.api.dal.HandlerTcpDisconnected;
import com.gg.reader.api.protocol.gx.*;
import com.zhijian.util.BeanUtil;
import com.zhijian.warehouse.manage.config.access.AccessConfig;
import com.zhijian.warehouse.manage.entity.po.AccessDoorDeploy;
import com.zhijian.warehouse.manage.entity.vo.request.AccessFacilityRecordSaveOrUpdateVo;
import com.zhijian.warehouse.manage.entity.vo.response.AccessDoorDeployVo;
import com.zhijian.warehouse.manage.enums.DoorEnum;
import com.zhijian.warehouse.manage.service.AccessOptService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class AccessUtil {
    private final static Logger logger = LoggerFactory.getLogger(AccessUtil.class);
    private static final byte RT_CODE = 0x00;
    private static final Map<String, GClient> DOOR_CLIENT_MAP = new HashMap<>();
    private static final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
    private final GClient doorClient;
    private final AccessConfig accessConfig;
    private final int delay = 1;
    private final String doorName = "通道门";
    private final AccessOptService accessOptService;
    private final AccessDoorDeployVo accessDoorDeployVo;
    
    public AccessUtil(GClient doorClient, AccessConfig accessConfig,
                      AccessOptService accessOptService, AccessDoorDeployVo accessDoorDeployVo
    ) {
        if (BeanUtil.isNotEmpty(accessDoorDeployVo) && DOOR_CLIENT_MAP.containsKey(accessDoorDeployVo.getIpAddr())) {
            GClient gClient = DOOR_CLIENT_MAP.get(accessDoorDeployVo.getIpAddr());
            gClient.setSendHeartBeat(false);//getIpAddr
            //gClient.close();//释放资源
            this.doorClient = doorClient; //重新赋值
            DOOR_CLIENT_MAP.put(accessDoorDeployVo.getIpAddr(), this.doorClient);
        }
        else {
            if (BeanUtil.isNotEmpty(accessDoorDeployVo))
                DOOR_CLIENT_MAP.put(accessDoorDeployVo.getIpAddr(), doorClient);
            this.doorClient = doorClient;
        }
        this.accessConfig = accessConfig;
        this.accessOptService = accessOptService;
        this.accessDoorDeployVo = accessDoorDeployVo;
        AtomicInteger atomicIntegerDoor = new AtomicInteger(0);
        if (this.accessConfig.getDoor().getEnable()) {
            reconnectWithRetry(doorClient, doorName, accessDoorDeployVo.getIpAddr(), accessConfig.getDoor().getTimeout(), atomicIntegerDoor);
        }
        else {
            accessOptService.updateDoorDeployStatus(AccessDoorDeploy.builder()
                    .id(accessDoorDeployVo.getId())
                    .status(DoorEnum.CONNECT_TYPE.OFF.value).build());
        }
    }
    
    
    public synchronized static void initAccess(GClient doorClient, AccessConfig accessConfig,
                                               AccessOptService accessFacilityRecordService, AccessDoorDeployVo accessDoorDeployVo
    
    ) {
        new AccessUtil(doorClient, accessConfig, accessFacilityRecordService, accessDoorDeployVo);
    }
    
    public synchronized static void closeAccess(Long id) {
        // new AccessUtil(id).closeAccessByDoorId(id);
    }
    
    private void stop(GClient client, String name) {
        MsgBaseStop msg = new MsgBaseStop();
        client.sendSynMsg(msg);
        if (RT_CODE == msg.getRtCode()) {
            logger.info("{} stop success", name);
        }
        else {
            logger.error("{} stop failed, rtCode:{}, rtMsg:{}", name, msg.getRtCode(), msg.getRtMsg());
        }
    }
    
    private void subscribeServerHandlerDoor() {
        doorClient.onTagEpcLog = new HandlerTagEpcLog() {
            @Override
            public void log(String s, LogBaseEpcInfo logBaseEpcInfo) {
                logger.info(String.format("%s ip:%s  doorLogBaseEpcInfo.epc:%s", doorName, accessDoorDeployVo.getIpAddr(), logBaseEpcInfo.getEpc()));
                AccessFacilityRecordSaveOrUpdateVo build = AccessFacilityRecordSaveOrUpdateVo.builder()
                        .rfId(logBaseEpcInfo.getEpc())
                        .doorIp(accessDoorDeployVo.getIpAddr().split(":")[0])
                        .build();
                accessOptService.saveDoorRecord(build, logBaseEpcInfo.getEpc());
                if (BeanUtil.isNotEmpty(build.getIsViolator()) && build.getIsViolator() == 1) {
                    //发送警报消息
                    setGpo(doorClient, 1, 0, 2000, true);
                }
                else if (BeanUtil.isNotEmpty(build.getIsViolator()) && build.getIsViolator() == 0) {
                    setGpo(doorClient, 1, 0, 2000, false);
                }
                //使通道门置为空闲状态
                //stop(doorClient, doorName);
            }
        };
    }
    
    
    private void setGpo(GClient client, int startGpo, int endGpo, int timeMillis, boolean isGpo1) {
        MsgAppSetGpo msg = new MsgAppSetGpo();
        
        // 根据 isGpo1 参数决定设置的是 GPO1 还是 GPO2
        if (isGpo1) {
            msg.setGpo1(startGpo); // 设置高电平 报警
        }
        else {
            msg.setGpo2(startGpo); // 设置高电平 绿灯
        }
        
        client.sendSynMsg(msg);
        if (0x00 == msg.getRtCode()) {
            logger.info("Set success");
        }
        else {
            logger.info("{}:{},  Set failed, rtCode:{}, rtMsg:{}",
                    isGpo1 ? "startGpo" : "endGpo", startGpo, msg.getRtCode(), msg.getRtMsg());
        }
        
        try {
            Thread.sleep(timeMillis);
        } catch (InterruptedException e) {
            logger.error("setGpo sleep error", e);
        }
        
        // 设置低电平
        if (isGpo1) {
            msg.setGpo1(endGpo);
        }
        else {
            msg.setGpo2(endGpo);
        }
        
        client.sendSynMsg(msg);
        if (0x00 == msg.getRtCode()) {
            logger.info("Set success");
        }
        else {
            logger.info("{}:{},  Set failed, rtCode:{}, rtMsg:{}",
                    isGpo1 ? "startGpo" : "endGpo", startGpo, msg.getRtCode(), msg.getRtMsg());
        }
    }
    
    //订阅TCP断开连接上报
    private void subscribeHandler(final GClient client, String name, String ipPort, int timeout) {
        client.onDisconnected = new HandlerTcpDisconnected() {
            @Override
            public void log(String s) {
                logger.info("{}{}已断开", name, s);
                client.setSendHeartBeat(false);
                client.close();//释放当前连接资源
                AtomicInteger retryCount = new AtomicInteger(0);
                reconnectWithRetry(client, name, ipPort, timeout, retryCount);
            }
        };
    }
    
    //重连接
    private void reconnectWithRetry(final GClient client, String name, String ipPort, int timeout, AtomicInteger retryCount) {
        Integer maxRetries = accessConfig.getDoor().getMaxRetries();
        if (retryCount.get() >= maxRetries) {
            client.setSendHeartBeat(false);
            client.close();
            logger.error("{}{}连接失败，已达到最大重试次数", name, ipPort);
            if (BeanUtil.isNotEmpty(accessDoorDeployVo)) {
                accessOptService.doorConnect(false, "请检查设备网络连接、设备端口及ip地址是否正确", accessDoorDeployVo.getId());
                //更新门状态
                accessOptService.updateDoorDeployStatus(AccessDoorDeploy.builder()
                        .id(accessDoorDeployVo.getId())
                        .status(DoorEnum.CONNECT_TYPE.OFF.value).build());
            }
        }
        else {
            scheduledExecutorService.schedule(new Runnable() {
                @Override
                public void run() {
                    synchronized (client) { //控制同一个客户端重复连接断开争夺资源
                        logger.info("{}连接尝试中...{} 每{}分钟重试一次 第{}次 将会在{}次后中断连接", name, ipPort, delay, retryCount.get(), accessConfig.getDoor().getMaxEnable() ? accessConfig.getDoor().getMaxRetries() : -1);
                        retry(client, name, ipPort, timeout, retryCount);
                    }
                }
            }, delay, TimeUnit.SECONDS); // 5000ms后重试
        }
    }
    
    private void retry(final GClient client, String name, String ipPort, int timeout, AtomicInteger retryCount) {
        // 尝试重新连接
        if (accessConfig.getDoor().getMaxEnable())
            retryCount.getAndIncrement();
        boolean flag = initClient(client, name, ipPort, timeout);
        if (!flag) {
            reconnectWithRetry(client, name, ipPort, timeout, retryCount); // 递归调用
        }
        else {
            logger.info("{}重连成功,ip:{}", name, ipPort);
            retryCount.set(0);// 重置重试计数
            if (BeanUtil.isNotEmpty(accessDoorDeployVo)) {
                accessOptService.doorConnect(true, "连接成功", accessDoorDeployVo.getId());
                accessOptService.updateDoorDeployStatus(AccessDoorDeploy.builder()
                        .id(accessDoorDeployVo.getId())
                        .status(DoorEnum.CONNECT_TYPE.CONNECT.value).build());
            }
            
            MsgBaseGetPower getPower = new MsgBaseGetPower();
            client.sendSynMsg(getPower);
            if (0x00 == getPower.getRtCode()) {
                MsgBaseGetPower getPower1 = getPower;
                logger.info("get power{} ip,{}", getPower1,accessDoorDeployVo.getIpAddr());
                Hashtable<Integer, Integer> dicPower = getPower1.getDicPower();
                if (dicPower.get(1) != 20) {
                    Hashtable<Integer, Integer> powers = new Hashtable<>();//{4=33, 3=33, 2=29, 1=28}
                    powers.put(1, 20);
                    powers.put(2, 20);
                    powers.put(3, 20);
                    powers.put(4, 20);
                    MsgBaseSetPower msg = new MsgBaseSetPower();
                    msg.setDicPower(powers);
                    client.sendSynMsg(msg);
                    if (0x00 == msg.getRtCode()) {
                        System.out.println("power Set success");
                    }else {
                        logger.info("power Set failed, rtCode:{}, rtMsg:{}", msg.getRtCode(), msg.getRtMsg());
                    }
                }
            }
            
        }
    }
    
    private boolean initClient(GClient client, String name, String ipPort, int timeout) {
        if (client.openTcp(ipPort, accessConfig.getDoor().getTimeout())) {
            //订阅通道门EPC日志上报
            subscribeServerHandlerDoor();
            //订阅TCP断开连接上报
            subscribeHandler(client, name, ipPort, timeout);
            logger.info("---------{}监听开启,ip:{}, ---------", name, ipPort);
            return true;
        }
        return false;
    }
    
    private synchronized void closeAccessByDoorId(String id) {
        GClient gClient = DOOR_CLIENT_MAP.get(id);
        if (BeanUtil.isNotEmpty(gClient)) {
            gClient.close();
            accessOptService.updateDoorDeployStatus(AccessDoorDeploy.builder()
                    .ipAddr(id)
                    .status(DoorEnum.CONNECT_TYPE.OFF.value).build());
        }
    }
    
}
